package org.cogaen.net;

import java.nio.ByteBuffer;

public class Header {
	
	public static final int SERIALIZED_SIZE = 16;
	private static final int INVALID_SEQUENCE_NUMBER = 0;
	private static final int SIZE_OF_BITFIELD = 32;

	/** Sequence number of this package. */
	private int seqNumber;

	/** Most recent acknowledged package. */
	private int ack;

	/** Bit field with subsequent acknowledged packages. */
	private int ackBitfield;

	/** Identifies the type of this package. */
	private int packageId = 0;
	
	public Header() {
		this.seqNumber = INVALID_SEQUENCE_NUMBER;
		reset();
	}
	
	public Header(ByteBuffer buffer) {
		deserialize(buffer);
	}
	
	public void serialize(ByteBuffer buffer) {
		buffer.putInt(this.packageId);
		buffer.putInt(this.seqNumber);
		buffer.putInt(this.ack);
		buffer.putInt(this.ackBitfield);		
	}
	
	public void deserialize(ByteBuffer buffer) {
		this.packageId = buffer.getInt();
		this.seqNumber = buffer.getInt();
		this.ack = buffer.getInt();
		this.ackBitfield = buffer.getInt();
	}
	
	public int getSequenceNumber() {
		return this.seqNumber;
	}
	
	public void setSequenceNumber(int seqNumber) {
		this.seqNumber = seqNumber;
	}
	
	public void incSequenceNumber() {
		this.seqNumber++;
	}
	
	public void setPackageId(int id) {
		this.packageId = id;
	}
	
	public int getPackageId() {
		return this.packageId;
	}
	
	public void reset() {
		this.ack = INVALID_SEQUENCE_NUMBER;
		this.ackBitfield = 0;
	}
	
	public boolean isAcknowledged(int seqNumber) {
		if (seqNumber > this.ack || seqNumber == INVALID_SEQUENCE_NUMBER) {
			return false;
		}
		
		if (seqNumber == this.ack) {
			return true;
		}

		int bit = this.ack - seqNumber - 1;
		if (bit >= SIZE_OF_BITFIELD) {
			return false;
		}
		
		return (this.ackBitfield & (1 << bit)) != 0;
	}
		
	public void acknowledge(int ackNumber) {
		if (ackNumber > this.ack) {
			int distance = ackNumber - this.ack;
			if ( distance < SIZE_OF_BITFIELD ) {
				this.ackBitfield <<= distance;				
			} else {
				this.ackBitfield = 0;
			}
			int lastAck = this.ack;
			this.ack = ackNumber;
			
			// set bit for previous highest acknowledge sequence number
			if (lastAck != INVALID_SEQUENCE_NUMBER) {
				acknowledge(lastAck);			
			}
			
		} else {
			int bit = this.ack - ackNumber - 1;
			if (bit >= SIZE_OF_BITFIELD) {
				return;
			}
			int mask = 1 << bit;
			this.ackBitfield |= mask;
		}
	}

}
